package cn.zhangfusheng.elasticsearch.dynamic;

import cn.zhangfusheng.elasticsearch.annotation.dsl.DslParams;
import cn.zhangfusheng.elasticsearch.annotation.dsl.dynamic.Dynamic;
import cn.zhangfusheng.elasticsearch.annotation.dsl.sql.DslWithSql;
import cn.zhangfusheng.elasticsearch.exception.GlobalSystemException;
import cn.zhangfusheng.elasticsearch.jexl.Jexl3Analysis;
import cn.zhangfusheng.elasticsearch.jexl.analysis.Analysis;
import cn.zhangfusheng.elasticsearch.repository.ElasticSearchRepository;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 动态字符串解析
 * @author fusheng.zhang
 * @date 2022-06-20 15:52:38
 */
public class DynamicAnalysis {

    private final Map<Method, SqlAnalysisDetail> methodAnalysisWithSql = new HashMap<>();
    private final Map<Method, DynamicAnalysisDetail> methodAnalysis = new HashMap<>();
    private final Map<Class<?>, Map<String, Object>> constantParamsCache = new HashMap<>();

    public DynamicAnalysisDetail analysis(Class<? extends ElasticSearchRepository<?>> clazz, Method method) {
        Dynamic dynamic = method.getAnnotation(Dynamic.class);
        if (Objects.isNull(dynamic)) return null;
        if (!methodAnalysis.containsKey(method)) {
            List<Analysis> analyses = new Jexl3Analysis(dynamic.value()).compile();
            List<String> parameterNames = getParameterWithIndex(method);
            Map<String, Object> constantParams = this.getConstantParams(clazz);
            DynamicAnalysisDetail sqlAnalysisDetail = new DynamicAnalysisDetail(constantParams, parameterNames, analyses, dynamic);
            methodAnalysis.put(method, sqlAnalysisDetail);
        }
        return methodAnalysis.get(method);
    }

    public SqlAnalysisDetail analysisWithSql(Class<?> clazz, Method method) {
        DslWithSql dslWithSql = method.getAnnotation(DslWithSql.class);
        if (Objects.isNull(dslWithSql)) return null;
        if (!methodAnalysis.containsKey(method)) {
            List<Analysis> analyses = new Jexl3Analysis(dslWithSql.value()).compile();
            List<String> parameterNames = getParameterWithIndex(method);
            Map<String, Object> constantParams = this.getConstantParams(clazz);
            SqlAnalysisDetail sqlAnalysisDetail = new SqlAnalysisDetail(constantParams, parameterNames, analyses, dslWithSql);
            methodAnalysisWithSql.put(method, sqlAnalysisDetail);
        }
        return methodAnalysisWithSql.get(method);
    }

    private List<String> getParameterWithIndex(Method method) {
        Parameter[] parameters = method.getParameters();
        List<String> parameterNames = new ArrayList<>(parameters.length);
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            DslParams annotation = parameter.getAnnotation(DslParams.class);
            if (Objects.nonNull(annotation) && StringUtils.isNotBlank(annotation.value())) {
                parameterNames.add(annotation.value());
            } else {
                parameterNames.add(String.format("arg_%s", i + 1));
            }
        }
        return parameterNames;
    }

    /**
     * 获取常量信息
     * @param clazz
     * @return
     */
    private Map<String, Object> getConstantParams(Class<?> clazz) {
        if (!constantParamsCache.containsKey(clazz)) {
            List<Class<?>> clazzList = new ArrayList<>(Arrays.asList(clazz.getInterfaces()));
            clazzList.add(clazz);
            Class<?> superclass = clazz.getSuperclass();
            while (Objects.nonNull(superclass)) {
                superclass = superclass.getSuperclass();
                if (Objects.nonNull(superclass)) clazzList.add(superclass);
            }
            Map<String, Object> constantParams = clazzList.stream()
                    .map(c -> Arrays.asList(c.getDeclaredFields()))
                    .flatMap(Collection::stream).filter(Objects::nonNull)
                    .filter(o -> !o.isSynthetic() && Modifier.isStatic(o.getModifiers()))
                    .collect(Collectors.toMap(Field::getName, o -> {
                        try {
                            o.setAccessible(true);
                            return o.get(null);
                        } catch (IllegalAccessException e) {
                            throw new GlobalSystemException(e);
                        } finally {
                            assert o != null;
                            o.setAccessible(false);
                        }
                    }));
            constantParamsCache.put(clazz, constantParams);
        }
        return constantParamsCache.get(clazz);
    }
}
